// CS 564: Project State 4 - HeapFile Manager
// 
// Caela Northey (caela) 905 653 2238
// Colin Laska   (laska) 906 150 7258
//
// For this stage, we will implement a File Manager for Heap Files
// that also provides a scan mechanism that will allow you to search
// a heap file for records that satisfy a search predicate called a
// filter. Note the main differences b/w the heap files and DB Minirel
// files:
//   - files at DB Minirel layer are PHYSICAL
//   - files at HeapFile level are LOGICAL
//
// We will implement 5 methods: createHeapFile(), HeapFile(),
// getRecord(), scanNext(), and insertRecord().

#include "heapfile.h"
#include "error.h"

// routine to create a heapfile:
//   1) call db->createfile()
//   2) allocate an empty page by invoking bm->allocPage().
//      returns a ptr to an empty page in the buffer pool and page #
//   3) cast this Page* pointer as a FileHdrPage* and initialize
//   4) call bm->allocPage() again -> first data page of file
//   5) invoke its init() method w Page* ptr returned to initialize
//      the page contents.
//   6) store page number of data page in firstPage and lastPage
//      of FileHrdPage
//   7) unpin both pages and mark as dirty
const Status createHeapFile(const string fileName)
{
    File*     file;
    Status     status;
    FileHdrPage*  hdrPage;
    int      hdrPageNo;
    int      newPageNo;
    Page*    newPage;

    // try to open the file. This should return an error
    status = db.openFile(fileName, file);

    if (status != OK)
    {
        // file doesn't exist. First create it and allocate
        // an empty header page and data page.
        status = db.createFile(fileName);
        if (status != OK)
        {
            return status;
        }

        status = db.openFile(fileName, file); //get file handle
        if (status != OK)
        {
            return status;
        }

        //Recall: allocPage(File* file, int& pageNo, Page*& page)
        //how should cast? temp page n c style?
        status = bufMgr->allocPage(file, hdrPageNo, (Page*&)hdrPage); 
        if (status != OK)
        {
            return status;
        }

        strcpy(hdrPage->fileName, fileName.c_str());
        hdrPage->recCnt = 0;

        //no need to cast here
        status = bufMgr->allocPage(file, newPageNo, newPage); 
        if (status != OK)
        {
            return status;
        }
  
        //Recall: void Page::init(int pageNo)
        newPage->init(newPageNo);
        hdrPage->firstPage = newPageNo;
        hdrPage->lastPage = newPageNo;
        hdrPage->pageCnt = 1;

        //Recall: unPinPage(File* file, const int PageNo,
        //                  const bool dirty)
        status = bufMgr->unPinPage(file, hdrPageNo, true);
        if (status != OK)
        {
            return status;
        }

        status = bufMgr->unPinPage(file, newPageNo, true);
        if (status != OK)
        {
            return status;
        }
     
        status = db.closeFile(file);    
    }
    return status;
}

// Good debugging code
// std::cout << "After 2nd unpinPage: " << status << std::endl;

// routine to destroy a heapfile
const Status destroyHeapFile(const string fileName)
{
  return (db.destroyFile(fileName));
}

// constructor opens the underlying file
//  1) opens appropriate file w/ db->openFile() (save in filePrt field)
//  2) reads and pins header page for that file in buffer pool AND
//     initializes (private) data members headerPage, headerPageNo,
//     hrdDirtyFlag (using file->getFirstPage() for pageNos)
//  3) read and pin first page of file into buffer pool AND
//     initializes curPage, curPageNo, and curDirtyFlag
//  4) set curRec to NULLRID
HeapFile::HeapFile(const string & fileName, Status& returnStatus)
{
    Status   status;
    FileHdrPage*  HdrPagePtr;
    Page*  pagePtr;
    int   firstPageNo;

    cout << "opening file " << fileName << endl;

    // open the file and read in the header page and the first data page
    if ((status = db.openFile(fileName, filePtr)) == OK)
    {
        status = filePtr->getFirstPage(firstPageNo);
        if (status != OK){
            returnStatus = status;
            return;
        }

        //Recall: readPage(File* file, const int PageNo, Page*& page);
        status = bufMgr->readPage(filePtr, firstPageNo, pagePtr);
        if (status != OK){
            returnStatus = status;
            return;
        }
    
        HdrPagePtr = (FileHdrPage *) pagePtr; //cast as FileHdrPage
        strcpy(HdrPagePtr->fileName, fileName.c_str());
        headerPage = HdrPagePtr;
        headerPageNo = firstPageNo;
        hdrDirtyFlag = false;

        status = bufMgr->readPage(filePtr, HdrPagePtr->firstPage,
                                  pagePtr);
        if (status != OK){
            returnStatus = status;
            return;
        }
        
        curPage = pagePtr;
        curPageNo = HdrPagePtr->firstPage;
        curDirtyFlag = false;
        curRec = NULLRID;
    
        returnStatus = status; //should be OK here
        return;
    }
    else
    {
      cerr << "open of heap file failed\n";
    returnStatus = status;
    return;
    }
}

// the destructor closes the file
HeapFile::~HeapFile()
{
    Status status;
    cout << "invoking heapfile destructor on file " << headerPage->fileName << endl;

    // see if there is a pinned data page. If so, unpin it 
    if (curPage != NULL)
    {
      status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
    curPage = NULL;
    curPageNo = 0;
    curDirtyFlag = false;
    if (status != OK) cerr << "error in unpin of date page\n";
    }
  
   // unpin the header page
    status = bufMgr->unPinPage(filePtr, headerPageNo, hdrDirtyFlag);
    if (status != OK) cerr << "error in unpin of header page\n";
  
  // status = bufMgr->flushFile(filePtr);  // make sure all pages of the file are flushed to disk
  // if (status != OK) cerr << "error in flushFile call\n";
  // before close the file
  status = db.closeFile(filePtr);
    if (status != OK)
    {
    cerr << "error in closefile call\n";
    Error e;
    e.print (status);
    }
}

// Return number of records in heap file

const int HeapFile::getRecCnt() const
{
  return headerPage->recCnt;
}

// retrieve an arbitrary record from a file.
//
// NOTE: curPage and curPageNo should be used to keep track of
//       the current data page pinned in buffer pool. SO:
//
//  1) if desired record on currently pinned page, simply invoke
//     curPage->getRecord(rid, rec) to get record
//  2) else, must unpin currently pinned page and use pageNo
//     of RID to read page into buffer pool
//
// RETURNS: a record via the given rec struct
const Status HeapFile::getRecord(const RID & rid, Record & rec)
{
    Status status;
    Page * recPage;

    if(rid.pageNo == curPageNo){
        status = curPage->getRecord(rid, rec);
        if (status != OK)
        {
            return status;
        }
        curRec = rid;
    }
    else
    {
        //Recall: unPinPage(File* file, const int PageNo, const bool
        // dirty);
        status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
        if (status != OK)
        {
            return status;
        }

       //Recall: readPage(File* file, const int PageNo, Page*& page);
       status = bufMgr->readPage(filePtr, rid.pageNo, recPage);
       if (status != OK)
       {
           return status;
       }

       curPage = recPage;
       curPageNo = rid.pageNo;
       curDirtyFlag = false; //just read in, no changes
     
       status = curPage->getRecord(rid, rec);
       curRec = rid; //last record returned

       if (status != OK)
       {
           return status;
       }

   }

    return status; //should be OK here
}

HeapFileScan::HeapFileScan(const string & name,
         Status & status) : HeapFile(name, status)
{
    filter = NULL;
}

const Status HeapFileScan::startScan(const int offset_,
             const int length_,
             const Datatype type_, 
             const char* filter_,
             const Operator op_)
{
    if (!filter_) {                        // no filtering requested
        filter = NULL;
        return OK;
    }
    
    if ((offset_ < 0 || length_ < 1) ||
        (type_ != STRING && type_ != INTEGER && type_ != FLOAT) ||
        (type_ == INTEGER && length_ != sizeof(int)
         || type_ == FLOAT && length_ != sizeof(float)) ||
        (op_ != LT && op_ != LTE && op_ != EQ && op_ != GTE && op_ != GT && op_ != NE))
    {
        return BADSCANPARM;
    }

    offset = offset_;
    length = length_;
    type = type_;
    filter = filter_;
    op = op_;

    return OK;
}


const Status HeapFileScan::endScan()
{
    Status status;
    // generally must unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
        curPage = NULL;
        curPageNo = 0;
    curDirtyFlag = false;
        return status;
    }
    return OK;
}

HeapFileScan::~HeapFileScan()
{
    endScan();
}

const Status HeapFileScan::markScan()
{
    // make a snapshot of the state of the scan
    markedPageNo = curPageNo;
    markedRec = curRec;
    return OK;
}

const Status HeapFileScan::resetScan()
{
    Status status;
    if (markedPageNo != curPageNo) 
    {
    if (curPage != NULL)
    {
      status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
      if (status != OK) return status;
    }
    // restore curPageNo and curRec values
    curPageNo = markedPageNo;
    curRec = markedRec;
    // then read the page
    status = bufMgr->readPage(filePtr, curPageNo, curPage);
    if (status != OK) return status;
    curDirtyFlag = false; // it will be clean
    }
    else curRec = markedRec;
    return OK;
}

// scan file one page at a time. for each page:
//  1) use firstRecord() and nextRecord() of Page class to get the
//     RIDs of all records on the page
//  2) convert RID to a pointer to record data and invoke matchRec()
//     to determine if record satisfies filter.
//       - if true: store RID in curRec and return curRec
//         (keep current page pinned until all recs of the page
//          have been processed). Then, continue w next page in file
//
// NOTE: since HeapFileScan class is derived from HeapFile, inherits
//       all methods
//
// RETURNS: RID of next record that satisfies the scan predicate
//          via the given outRID param
//          OK if no errors
//          error code of first error that occured
#include <stdio.h>
#include "stdlib.h"

const Status HeapFileScan::scanNext(RID& outRid)
{
    Status   status = OK;
    RID    nextRid;
    RID    tmpRid;
    int   nextPageNo;
    Record      rec;

    tmpRid = curRec;  // Store the starting Rid

    while(1)
    {
        // Try checking the next RID in the page
        status = curPage->nextRecord(tmpRid, nextRid);

      if (status == ENDOFPAGE)
      {

         // Flag is used to find a page that has records on it
         bool flag = false; // Assume current page has no records
         while(!flag){

            // Get the next page with records
            status = curPage->getNextPage(nextPageNo);
            if (nextPageNo == -1)
            {
                return FILEEOF;
            }

            // Unpin old page
            status = bufMgr->unPinPage(filePtr, curPageNo,
                                       curDirtyFlag);
            if (status != OK)
            {
                std::cout << "Fail 1: " << curPageNo << "\n";
                return status;
            }

            // Read in next page
            status = bufMgr->readPage(filePtr, nextPageNo, curPage);
            if (status != OK)
            {
            std::cout << "Fail 3\n";
                return status;
            }

            // Set curPageNo to the next one and get the first record
            curPageNo = nextPageNo;

            status = curPage->firstRecord(nextRid);
            if(status == NORECORDS){
                flag = false; // This page is valid but has no records
            } else if (status != OK){
                return status;
            } else {
                flag = true;

            }

         } //end inner while

       }//end if ENDOFPAGE

       // Compare the scan parameter
       status = HeapFile::getRecord(nextRid, rec);

       if (status != OK)
       {
           return status;
       }

        // Compare to filter
       if(matchRec(rec))
       {
           curRec = nextRid;
           outRid = curRec;
           return OK;
       }
  
       // Iterate to next RID
       tmpRid = nextRid;

    } // End while(1) loop
}


// returns pointer to the current record.  page is left pinned
// and the scan logic is required to unpin the page 

const Status HeapFileScan::getRecord(Record & rec)
{
    return curPage->getRecord(curRec, rec);
}

// delete record from file. 
const Status HeapFileScan::deleteRecord()
{
    Status status;

    // delete the "current" record from the page
    status = curPage->deleteRecord(curRec);
    curDirtyFlag = true;

    // reduce count of number of records in the file
    headerPage->recCnt--;
    hdrDirtyFlag = true; 
    return status;
}


// mark current page of scan dirty
const Status HeapFileScan::markDirty()
{
    curDirtyFlag = true;
    return OK;
}

const bool HeapFileScan::matchRec(const Record & rec) const
{
    // no filtering requested
    if (!filter) return true;

    // see if offset + length is beyond end of record
    // maybe this should be an error???
    if ((offset + length -1 ) >= rec.length)
  return false;

    float diff = 0;                       // < 0 if attr < fltr
    switch(type) {

    case INTEGER:
        int iattr, ifltr;                 // word-alignment problem possible
        memcpy(&iattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ifltr,
               filter,
               length);
        diff = iattr - ifltr;
        break;

    case FLOAT:
        float fattr, ffltr;               // word-alignment problem possible
        memcpy(&fattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ffltr,
               filter,
               length);
        diff = fattr - ffltr;
        break;

    case STRING:
        diff = strncmp((char *)rec.data + offset,
                       filter,
                       length);
        break;
    }

    switch(op) {
    case LT:  if (diff < 0.0) return true; break;
    case LTE: if (diff <= 0.0) return true; break;
    case EQ:  if (diff == 0.0) return true; break;
    case GTE: if (diff >= 0.0) return true; break;
    case GT:  if (diff > 0.0) return true; break;
    case NE:  if (diff != 0.0) return true; break;
    }

    return false;
}

InsertFileScan::InsertFileScan(const string & name,
                               Status & status) : HeapFile(name, status)
{
  //Do nothing. Heapfile constructor will bread the header page and the first
  // data page of the file into the buffer pool
}

InsertFileScan::~InsertFileScan()
{
    Status status;
    // unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, true);
        curPage = NULL;
        curPageNo = 0;
        if (status != OK) cerr << "error in unpin of data page\n";
    }
}

// Insert a record described by rec into the file
// RETURNS the RID of the inserted record into outRID
const Status InsertFileScan::insertRecord(const Record & rec, RID& outRid)
{
    Page*  newPage;
    int    newPageNo;
    Status  status, unpinstatus;
    RID    rid;

    // check for very large records
    if ((unsigned int) rec.length > PAGESIZE-DPFIXED)
    {
        // will never fit on a page, so don't even bother looking
        return INVALIDRECLEN;
    }

    // Unpin old full page
    unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
    if (unpinstatus != OK)
    {
        return unpinstatus;
    }

    status = bufMgr->readPage(filePtr, headerPage->lastPage, curPage);
    if (status != OK)
    {
        return status;
    }
    curPageNo = headerPage->lastPage;
    //curDirtyFlag = false;

    status = curPage->insertRecord(rec, rid);
    outRid = rid;

    // If there is no space for a record on the last page,
    // make a new one
    if (status == NOSPACE)
    {
        // Unpin old full page
        unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
        if (unpinstatus != OK)
        {
            return unpinstatus;
        }

        status = bufMgr->allocPage(filePtr, newPageNo, newPage); 
        if (status != OK)
        {
            return status;
        }

        // Reset values to point to this new page
        newPage->init(newPageNo);
        curPage->setNextPage(newPageNo);
        headerPage->lastPage = newPageNo;
        curPage = newPage;
        curPageNo = newPageNo;

        // Try insert again
        status = curPage->insertRecord(rec, rid);
        outRid = rid;

        if (status != OK)
        {
            return status;
        }

    }
  
    curDirtyFlag = true;
    return status;  
}
